package MicroSpring.beanPostProcessor.impl;

import MicroSpring.annotation.aop.After;
import MicroSpring.annotation.aop.Around;
import MicroSpring.annotation.aop.Before;
import MicroSpring.annotation.aop.PointCut;
import MicroSpring.aopProxy.pjp.ProceedingJoinPoint;
import MicroSpring.aopProxy.proxy.builder.EnhancerBuilder;
import MicroSpring.aopProxy.proxy.builder.ProxyBuilder;
import MicroSpring.beanFactory.impl.BeanDefinition;
import MicroSpring.beanFactory.impl.DefaultListableBeanFactory;
import MicroSpring.beanPostProcessor.BeanPostProcessor;
import MicroSpring.beanPostProcessor.InstantiationAwareBeanPostProcessor;
import MicroSpring.common.IOUtils;
import MicroSpring.common.PoinCutDefinition;
import MicroSpring.common.PointCutUtils;
import MicroSpring.common.StringUtils;
import MicroSpring.exception.BeansException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/*
    处理aop代理bean处理器
 */
public class AnnotationAwareAspectJAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanPostProcessor {
    private final DefaultListableBeanFactory beanFactory;
    private boolean isJdkProxy;

    public AnnotationAwareAspectJAutoProxyCreator(DefaultListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public void setJdkProxy(boolean jdkProxy) {
        isJdkProxy = jdkProxy;
    }

    /*
            处理@Aspect切面,获取代理对象，交给最后的ImportAwareBeanPostProcessor 去替换成代理对象
         */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinitionMap().get(beanName);
        if (!beanDefinition.isAspect())
            return bean;

        Class<?> clazz = bean.getClass();

        List<PoinCutDefinition>poinCutDefinitionList=new LinkedList<>();

        //处理切点表达式
        for (Method m : clazz.getDeclaredMethods()) {
            PoinCutDefinition poinCutDefinition=null;
            if (m.isAnnotationPresent(Around.class)){
                Around a = m.getAnnotation(Around.class);
                String value = a.value();
                poinCutDefinition= PointCutUtils.resolve(value);
                if (poinCutDefinition!=null){
                    poinCutDefinition.setAround(true);
                }
            }else if (m.isAnnotationPresent(Before.class)){
                Before b = m.getAnnotation(Before.class);
                String value = b.value();
                poinCutDefinition=PointCutUtils.resolve(value);
                if (poinCutDefinition!=null)
                    poinCutDefinition.setBefore(true);
            }else if (m.isAnnotationPresent(After.class)){
                After af = m.getAnnotation(After.class);
                String value = af.value();
                poinCutDefinition=PointCutUtils.resolve(value);
                if (poinCutDefinition!=null)
                    poinCutDefinition.setAfter(true);
            }else if (m.isAnnotationPresent(PointCut.class)){
                PointCut p = m.getAnnotation(PointCut.class);
                String value = p.value();
                poinCutDefinition= PointCutUtils.resolve(value);
                if (poinCutDefinition!=null)
                    poinCutDefinition.setPointCut(true);
            }

            if (poinCutDefinition!=null){
                poinCutDefinition.setMethod(m);
                if (poinCutDefinition.isPointCut())
                    poinCutDefinitionList.add(0,poinCutDefinition);
                else
                    poinCutDefinitionList.add(poinCutDefinition);
            }

        }

        //前面已排序,pointCut的标注方法先处理。获得目标方法,并且获取代理对象后注入到bean工厂,交由后续处理
        Map<String, Map<Class,Method[]>> poinCutClassMethods=new ConcurrentHashMap<>();
        for (PoinCutDefinition poinCutDefinition : poinCutDefinitionList) {

            try {
                String annoName = poinCutDefinition.getAnnoName();
                if (StringUtils.hasText(annoName)){
                    findWhereInUseAnno(annoName,poinCutDefinition);
                }else {
                    buildTargetMethods(bean,poinCutDefinition);
                }

                if (poinCutDefinition.isPointCut()){
                    poinCutClassMethods.put(poinCutDefinition.getMethod().getName(),poinCutDefinition.getClassMethods());
                }
                else if (poinCutDefinition.isBefore()){
                    if (poinCutDefinition.isTargetToPointCut()){
                        dealWithBefore(bean,poinCutDefinition.getMethod(),poinCutClassMethods.get(poinCutDefinition.getPointCutMethodName()));
                    }
                    else
                        dealWithBefore(bean,poinCutDefinition.getMethod(),poinCutDefinition.getClassMethods());
                }
                else if (poinCutDefinition.isAfter()){
                    if (poinCutDefinition.isTargetToPointCut()){
                        dealWithAfter(bean,poinCutDefinition.getMethod(),poinCutClassMethods.get(poinCutDefinition.getPointCutMethodName()));
                    }
                    else
                        dealWithAfter(bean,poinCutDefinition.getMethod(),poinCutDefinition.getClassMethods());
                }
                else if (poinCutDefinition.isAround()){
                    if (poinCutDefinition.isTargetToPointCut()){
                        dealWithAround(bean,poinCutDefinition.getMethod(),poinCutClassMethods.get(poinCutDefinition.getPointCutMethodName()));
                    }
                    else
                        dealWithAround(bean,poinCutDefinition.getMethod(),poinCutDefinition.getClassMethods());
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("处理AOP目标方法时未找到目标类.检查切点表达式是否书写正确");
            }
        }
        return bean;
    }

    //需要查找哪些方法使用了该注解
    private void findWhereInUseAnno(String annoName, PoinCutDefinition poinCutDefinition) {
        for (Map.Entry<Class, Map<Annotation, Method>> entry : beanFactory.getTypeMethodAnnotations().entrySet()) {
            Class clazz = entry.getKey();
            Map<Annotation, Method> mp = entry.getValue();
            List<Method>ms=new ArrayList<>();
            for (Map.Entry<Annotation, Method> e : mp.entrySet()) {
                Annotation a = e.getKey();
                if (a.annotationType().getName().equals(annoName)){
                    Method m = e.getValue();
                    ms.add(m);
                }
            }
            Method[] mms=new Method[ms.size()];
            int idx=0;
            for (Method m : ms) {
                mms[idx++]=m;
            }
            if (idx>0)
                poinCutDefinition.addClassMethods(clazz,mms);
        }
    }


    private void dealWithAround(Object bean, Method aroundMethod, Map<Class, Method[]> classMethods) {
        Parameter[] parameters = aroundMethod.getParameters();
        Parameter pjpParameter = parameters[0];
        if (parameters==null || parameters.length==0
                || !(pjpParameter.getParameterizedType() == ProceedingJoinPoint.class)){
            throw new BeansException("pjp参数缺失,需要为around() 注入pjp.");
        }
        //注入pjp
        for (Map.Entry<Class, Method[]> entry : classMethods.entrySet()) {
            Class clazz = entry.getKey();
            Method[] methods = entry.getValue();
            String beanName = beanFactory.getSingletonBeanNameOfType(clazz);
            Object targetBean = beanFactory.getAspectBeans().containsKey(beanName)? beanFactory.getAspectBeans().get(beanName):beanFactory.getBean(clazz);
            Class[] interfaces = clazz.getInterfaces();
            Object aspectInstance=null;
            if (isJdkProxy){
                //走jdk代理
                ProxyBuilder proxyBuilder=new ProxyBuilder(beanFactory.getClassLoader(),
                        interfaces,aroundMethod,targetBean,bean);
                for (Method method : methods) {
                    proxyBuilder.addTargetMethodMap(method.getName());
                }
                aspectInstance = proxyBuilder.getAroundProxyInstance();

            }else {
                //走cglib代理
                EnhancerBuilder enhancerBuilder=new EnhancerBuilder(clazz,targetBean,aroundMethod,bean);
                for (Method method : methods) {
                    enhancerBuilder.addTargetMethodMap(method.getName());
                }
                aspectInstance=enhancerBuilder.getAroundProxyInstance();
            }
            beanFactory.addAspectBean(beanName,aspectInstance);
        }

    }

    private void dealWithAfter(Object aspectBean, Method afterMethod, Map<Class, Method[]> classMethods) {
        if (classMethods==null)
            return;
        for (Map.Entry<Class, Method[]> entry : classMethods.entrySet()) {
            Class clazz = entry.getKey();
            String beanName = beanFactory.getSingletonBeanNameOfType(clazz);
            Object targetBean = beanFactory.getAspectBeans().containsKey(beanName)? beanFactory.getAspectBeans().get(beanName) : beanFactory.getBean(clazz);
            Class[] interfaces = clazz.getInterfaces();
            Object aspectInstance=null;
            if (isJdkProxy){
                //走jdk代理
                ProxyBuilder proxyBuilder=new ProxyBuilder(beanFactory.getClassLoader(),
                        interfaces,afterMethod,targetBean,aspectBean);
                for (Method m : entry.getValue()) {
                    proxyBuilder.addTargetMethodMap(m.getName());
                }
                aspectInstance = proxyBuilder.getAfterProxyInstance();
            }else {
                //走cglib代理
                EnhancerBuilder enhancerBuilder=new EnhancerBuilder(clazz,targetBean,afterMethod,aspectBean);
                for (Method method : entry.getValue()) {
                    enhancerBuilder.addTargetMethodMap(method.getName());
                }
                aspectInstance=enhancerBuilder.getAfterProxyInstance();
            }
            beanFactory.addAspectBean(beanName,aspectInstance);
        }
    }

    private void dealWithBefore(Object aspectBean, Method beforeMethod, Map<Class, Method[]> classMethods) {
        if (classMethods==null)
            return;
        for (Map.Entry<Class, Method[]> entry : classMethods.entrySet()) {
            Class clazz = entry.getKey();
            String beanName = beanFactory.getSingletonBeanNameOfType(clazz);
            Object targetBean = beanFactory.getAspectBeans().containsKey(beanName) ?
                    beanFactory.getAspectBeans().get(beanName):beanFactory.getBean(clazz);

            Class[] interfaces = clazz.getInterfaces();
            Object aspectInstance=null;
            if (isJdkProxy){
                //走jdk代理
                ProxyBuilder<Object> proxyBuilder=new ProxyBuilder(beanFactory.getClassLoader(),
                        interfaces,beforeMethod,targetBean,aspectBean);
                for (Method m : entry.getValue()) {
                    proxyBuilder.addTargetMethodMap(m.getName());
                }
                aspectInstance = proxyBuilder.getBeforeProxyInstance();
            }else {
                //走cglib代理
                EnhancerBuilder enhancerBuilder=new EnhancerBuilder(clazz,targetBean,beforeMethod,aspectBean);
                for (Method method : entry.getValue()) {
                    enhancerBuilder.addTargetMethodMap(method.getName());
                }
                aspectInstance=enhancerBuilder.getBeforeProxyInstance();
            }
            beanFactory.addAspectBean(beanName,aspectInstance);
        }
    }

    private void buildTargetMethods(Object bean, PoinCutDefinition poinCutDefinition) throws ClassNotFoundException {
        if (poinCutDefinition.isTargetClass()){
            buildTargetMethodsByWithin(poinCutDefinition);
            return;
        }
        String declaringTypePattern = poinCutDefinition.getDeclaringTypePattern();
        String namePattern = poinCutDefinition.getNamePattern();
        String paramPattern = poinCutDefinition.getParamPattern();
        ClassLoader classLoader=beanFactory.getClassLoader();
        Class [] paramTypes=PointCutUtils.getParamTypes(paramPattern,classLoader);
        if (declaringTypePattern==null || "".equals(declaringTypePattern)){
            //代表在当前类中,也就是bean
            Class<?> clazz = bean.getClass();
            Method[] methods=PointCutUtils.getTargetMethodsByClass(clazz,namePattern,paramTypes);
            poinCutDefinition.addClassMethods(clazz,methods);
        }else {
            //可能是com.xx 确切的类
            if (declaringTypePattern.charAt(declaringTypePattern.length()-1)!='*'){
                Class<?> clazz = classLoader.loadClass(declaringTypePattern);
                Method[] methods=PointCutUtils.getTargetMethodsByClass(clazz,namePattern,paramTypes);
                poinCutDefinition.addClassMethods(clazz,methods);
            }else {
                char[] chars = declaringTypePattern.toCharArray();
                List<Class> classList=null;
                if (chars[chars.length-2]=='.' && chars[chars.length-3]=='.'){
                     classList = IOUtils.scanClassForPackgeName(declaringTypePattern, true);
                }else {
                     classList = IOUtils.scanClassForPackgeName(declaringTypePattern, false);
                }
                if (classList!=null){
                    for (Class clazz : classList) {
                        Method[] methods = PointCutUtils.getTargetMethodsByClass(clazz, namePattern, paramTypes);
                        poinCutDefinition.addClassMethods(clazz,methods);
                    }
                }
            }
        }
    }

    private void buildTargetMethodsByWithin(PoinCutDefinition poinCutDefinition) {
        //直接扫描包下的类
        String packageName = poinCutDefinition.getPackageName();
        List<Class> classList = IOUtils.scanClassForPackgeName(packageName, true);
        if (classList!=null){
            for (Class clazz : classList) {
                Method[] methods = PointCutUtils.getTargetMethodsByClass(clazz, "*", null);
                poinCutDefinition.addClassMethods(clazz,methods);
            }
        }
    }

}
